package com.qrlib;


import android.Manifest.permission;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.content.ContextCompat;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import com.qrlib.utils.CameraUtils;
import java.io.IOException;
import java.util.List;

  /*

# normal
lifecycle: create start           resume                                    pause                                      stop            destroy
                  create camera   create surface                            destroy surface                            release camera
surface:                                          created   changed                     (destroyed)
                                                            restart preview             stop preview
  */

/**
 * 异步风格的 Camera 管理器 Created by zhengxiao on 17/08/2017.
 */

public class CameraPreviewViewController implements Callback, CameraUtils.Callback {

  private static final boolean ENABLE_ASYNC_OPEN_CAMERA = true; // 异步方式打开 camera
  private boolean userStartedPreview = false;

  public boolean isTorchAvailable() {
    return camera != null && CameraUtils.isFlashLightSupported(camera) && context
        .getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
  }

  private Handler mainHandler = new Handler(Looper.getMainLooper());

  @Override
  public void onCameraOpened(final Camera newCamera) {
    mainHandler.post(() -> {
      isOpeningCamera = false;
      onCameraReady(newCamera);
    });
  }


  public interface Callback {

    void onStartPreview(CameraPreviewViewController manager);

    void onCameraUnavailable(CameraPreviewViewController manager);
  }

  private final Activity context;
  private Callback callback;
  private int cameraRotation;

  public boolean isPreviewing() {
    return isPreviewStarted;
  }

  public int getCameraRotation() {
    return cameraRotation;
  }

  private boolean isOpeningCamera = false;      // 0
  private boolean isSurfaceCreated = false;     // 1
  private boolean isCameraParameterSet = false; // 2
  private boolean isPreviewStarted = false;     // 3
  private PreviewCallback oneShotPreviewCallback;

  private final SurfaceView surfacePreview;
  private Camera camera;
  private SurfaceHolder holder;
  private byte[] buffer;

  public CameraPreviewViewController(@NonNull Activity context, @NonNull SurfaceView surfacePreview,
      @Nullable Callback callback) {
    this.context = context;
    this.surfacePreview = surfacePreview;
    this.callback = callback;

    holder = surfacePreview.getHolder();
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    holder.addCallback(this);
  }

  public void destroy() {
    if (holder != null) {
      holder.removeCallback(this);
      surfaceDestroyed(null); // manually call, because removing callback will not trigger it
    }
  }


  public Camera getCamera() {
    return camera;
  }

  private void setupCameraParameters() {
    if (camera != null) {
      try {
        Display display = context.getWindowManager().getDefaultDisplay();
        int displayRotation = display.getRotation();
        cameraRotation = CameraUtils.getRotation(displayRotation, CameraInfo.CAMERA_FACING_BACK);
        Timber.d("setting cameraRotation: %s", cameraRotation);
        camera.setDisplayOrientation(cameraRotation);

        Parameters params = camera.getParameters();
        int screenW = AndroidUiBasicsKt.getScreenWidth();
        int screenH = AndroidUiBasicsKt.getScreenHeight();
        Size size;
        if (cameraRotation == 0 || cameraRotation == 180) {
          size = CameraUtils.getOptimalPreviewSize(camera, screenW, screenH);
        } else {
          size = CameraUtils.getOptimalPreviewSize(camera, screenH, screenW);
        }
        params.setPreviewSize(size.width, size.height);
        List<String> focusMode = (params.getSupportedFocusModes());
        if (focusMode.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
          params.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        } else {
          params.setFocusMode(Parameters.FOCUS_MODE_AUTO);
        }
        int bufSize = size.width * size.height * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;
        buffer = new byte[bufSize];
//        int maxMeteringAreas = params.getMaxNumMeteringAreas();
//        if (maxMeteringAreas > 0) {
//          List<Area> meteringAreas = new ArrayList<>();
//          meteringAreas.add(new Area(new Rect(-10, -10, 10, 10), 1000));
//          params.setMeteringAreas(meteringAreas);
//        }

        camera.setParameters(params);
      } catch (Exception e) {
        Timber.e(e, "setting camera parameter failed");
      }
    }
  }


  @UiThread
  @Override
  public void surfaceCreated(SurfaceHolder holder) {
    Timber.d("surfaceCreated: %s", Thread.currentThread().getName());
    isSurfaceCreated = true;
  }

  @UiThread
  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    Timber.d("surfaceChanged: %s", Thread.currentThread().getName());
    if (holder.getSurface() == null) {
      return;
    }
    try {
      doStartPreview();
    } catch (RuntimeException e) {
      getAndroidApp().printError(e);
    }
  }

  @UiThread
  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
    Timber.d("surfaceDestroyed: %s", Thread.currentThread().getName());
    // caution: this method may be never called!!! 'cause callback is likely be removed when
    // onPause()
    // we call stopPreview() here only to make sure we will stop preview.
    isSurfaceCreated = false;
    doStopPreview();
  }

  @UiThread
  public void startPreview(PreviewCallback previewCallback) {
    this.oneShotPreviewCallback = previewCallback;
    userStartedPreview = true;
    try {
      doStartPreview();
    } catch (RuntimeException e) {
      getAndroidApp().printError(e);
    }
  }

  // 调整相机参数
  public void adjustCameraParameter(int step) {
    Camera.Parameters parameters = camera.getParameters();
    final int maxZoom = parameters.getMaxZoom();

    int zoom = parameters.getZoom();
    int max = maxZoom / 3;

    if (parameters.isZoomSupported()) {

      if (step < max) {
        zoom += step;
        if (zoom > max) {
          zoom = max;
        }
      } else {
        zoom = max;
      }
      parameters.setZoom(zoom);
      camera.setParameters(parameters);

    }
  }

  public void requestOneShotPreview(PreviewCallback callback) {
    this.oneShotPreviewCallback = callback;
    try {
      doStartPreview();
    } catch (RuntimeException e) {
      getAndroidApp().printError(e);
    }
  }

  private void doStartPreview() throws RuntimeException {
    if (!userStartedPreview) {
      return;
    }
    if (camera == null) {
      if (!isOpeningCamera) {
        if (ContextCompat.checkSelfPermission(context, permission.CAMERA)
            == PackageManager.PERMISSION_DENIED) {
          mainHandler.post(new Runnable() {
            @Override
            public void run() {
              if (callback != null) {
                callback.onCameraUnavailable(CameraPreviewViewController.this);
              }
            }
          });
          Timber.w("use has no camera permission");
          return;
        }
        if (ENABLE_ASYNC_OPEN_CAMERA) {
          CameraUtils.openAsync(CameraInfo.CAMERA_FACING_BACK, this);
          isOpeningCamera = true;
        } else {
          onCameraReady(CameraUtils.open(CameraInfo.CAMERA_FACING_BACK));
        }
        Timber.w("doStartPreview: camera is not opened, waiting onCameraOpened()");
        // !!! wait async on onCameraOpened()
      }
      return;
    }

    if (!isSurfaceCreated) {
      Timber.w("doStartPreview: surface is not created, waiting surface callback()");
      // !!! wait surface callback, then call doStartPreview() again
      return;
    }

    // set preview display parameters
    if (!isCameraParameterSet) {
      setupCameraParameters();
      isCameraParameterSet = true;
    }

    // startPreview
    try {
      if (!isPreviewStarted) {
        camera.setPreviewDisplay(surfacePreview.getHolder());
        camera.startPreview();
        Timber.w("doStartPreview: startPreview()");
        if (callback != null) {
          callback.onStartPreview(this);
        }
        isPreviewStarted = true;
      }

      if (oneShotPreviewCallback != null) {
        //setOneShotPreviewCallback()会每次频繁new frame byte[]并且不能及时释放  不能在需要实时多次取图时使用
        camera.setPreviewCallbackWithBuffer(oneShotPreviewCallback);
        camera.addCallbackBuffer(buffer);
        oneShotPreviewCallback = null;
      }

    } catch (IOException e) {
      new Throwable("doStartPreview failed when startPreview", e).printStackTrace();
      mainHandler.post(new Runnable() {
        @Override
        public void run() {
          if (callback != null) {
            callback.onCameraUnavailable(CameraPreviewViewController.this);
          }
        }
      });
    }

  }

  public void stopPreview() {
    Timber.d("stopPreview: ");
    userStartedPreview = false;
    if (camera != null) {
      try {
        doStopPreview();
      } catch (Exception e) {
        Timber.e(e, "failed to doStopPreview");
      }
    }
  }

  private void doStopPreview() {
    Timber.d("doStopPreview: ");
    if (camera != null && isPreviewStarted) {
      mainHandler.removeCallbacksAndMessages(null);
      camera.cancelAutoFocus();
      camera.setPreviewCallbackWithBuffer(null);
      camera.stopPreview();
      isPreviewStarted = false;
      oneShotPreviewCallback = null;
    }
  }

  public synchronized void onCameraReady(Camera camera) {
    // continue;
    Timber.d("onCameraReady: ");
    if (camera != null) {
      this.camera = camera;
      try {
        doStartPreview();
      } catch (RuntimeException e) {
        getAndroidApp().printError(e);
      }
    } else {
      if (callback != null) {
        Timber.d("onCameraReady: callback.onCameraUnavailable");
        callback.onCameraUnavailable(this);
      }
    }
  }

  public synchronized void releaseCamera() {
    Timber.d("releaseCamera: ");
    mainHandler.removeCallbacksAndMessages(null);
    if (camera != null) {
      try {
        camera.release();
        camera = null;
        isCameraParameterSet = false;
      } catch (Exception e) {
        Timber.e(e, "failed to releaseCamera");
      }
    }
  }

  public boolean toggleTorch(boolean enable) {
    if (!userStartedPreview) {
      return true;
    }
    if (!(camera != null && isSurfaceCreated && isTorchAvailable())) {
      return false;
    }

    // turn on flashlight
    if (enable) {
      try {
        Parameters p = camera.getParameters();
        p.setFlashMode(Parameters.FLASH_MODE_TORCH);
        camera.setParameters(p);
        camera.startPreview();
      } catch (Exception e) {
        // do nothing
      }
    } else {
      try {
        Parameters p = camera.getParameters();
        p.setFlashMode(Parameters.FLASH_MODE_OFF);
        camera.setParameters(p);
        camera.startPreview();
      } catch (Exception e) {
        // do nothing
      }
    }

    return true;
  }
}
